【ARM Cortex-M 开发实战指南(基础篇)】第1章 初识GPIO流水灯

1.1 GPIO工作原理

熟悉单片机的朋友都知道,学习的第一个例程就是流水灯,要想实现流水灯,首先必须了解GPIO的工作原理。GPIO的基本结构如图所示。

LwDFYj.md.png

STM32 的 IO 口可以由软件配置成如下 8 种模式:

输入模式
浮空输入:浮空(floating)就是逻辑器件的输入引脚即不接高电平,也不接低电平。由于逻辑器件的内部结构,当它输入引脚悬空时,相当于该引脚接了高电平。一般实际运用时,引脚不建议悬空,易受干扰。 通俗讲就是让管脚什么都不接,浮空着。信号进入芯片内部后,既没有接上拉电阻也没有接下拉电阻,经由触发器输入。配置成这个模式后,用电压变量引脚电压为1点几伏,这是个不确定值。由于其输入阻抗比较大,一般把这种模式用于标准的通讯协议,比如IIC、USART的等。该模式是STM32复位之后的默认模式。

LwDeXV.md.png

上拉输入:上拉就是把电位拉高,比如拉到Vcc。上拉就是将不确定的信号通过一个电阻嵌位在高电平,电阻同时起限流作用,弱强只是上拉电阻的阻值不同,没有什么严格区分。上拉输入就是信号进入芯片后加了一个上拉电阻,再经过施密特触发器转换成0、1信号,读取此时的引脚电平为高电平;

LwDK7F.md.png

下拉输入:就是把电压拉低,拉到GND。与上拉原理相似。下拉输入就是信号进入芯片后加了一个下拉电阻,再经过施密特触发器转换成0、1信号,读取此时的引脚电平为低电平;

LwD1h9.md.png

模拟输入:信号进入后不经过上拉电阻或者下拉电阻,关闭施密特触发器,经由另一线路把电压信号传送到片上外设模块。模拟输入是指传统方式的输入,数字输入是输入PCM数字信号,即0、1的二进制数字信号,通过数模转换,转换成模拟信号,经前级放大进入功率放大器,功率放大器还是模拟的。比如传送给ADC模块,由ADC采集电压信号。所以可以理解为模拟输入的信号是未经处理的信号,是原汁原味的信号。

LwD8pR.md.png

输出模式
开漏输出:一般用在电平不匹配的场合,如需要输出5V的高电平。输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。

LwDYX6.md.png

复用开漏输出:可以理解为GPIO口被用作第二功能时的配置情况(即并非作为通用IO口使用)。端口必须配置成复用开漏功能输出模式。

LwDU0O.md.png

推挽式输出:可以输出高、低电平,连接数字器件;推挽结构一般是指两个三极管分别受两个互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由IC的电源决定。推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中,各负责正负半周的波形放大任务,电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。输出既可以向负载灌电流,也可以从负载抽取电流。推拉式输出级既提高电路的负载能力,又提高开关速度。

LwDr9A.md.png

推挽式复用输出

LwDyct.md.png

1.2 I/O复用和重映射

1.2.1 I/O复用

STM32 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个 GPIO如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。当I/O端口被配置为复用功能时:
● 在开漏或推挽式配置中,输出缓冲器被打开
● 内置外设的信号驱动输出缓冲器(复用功能输出)
● 施密特触发输入被激活
● 弱上拉和下拉电阻被禁止
● 在每个APB2时钟周期,出现在I/O脚上的数据被采样到输入数据寄存器
● 开漏模式时,读输入数据寄存器时可得到I/O口状态
● 在推挽模式时,读输出数据寄存器时可得到最后一次写的值

LwD6jP.md.png

大家都知道, MCU 都有串口, STM32 有好几个串口。比如说 STM32F103ZET6 有 5 个串口,我们可以查手册知道,串口 1 的引脚对应的 IO 为 PA9,PA10.PA9, PA10 默认功能是 GPIO, 所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用。

复用功能 GPIO
USART1_TX PA9
USART1_RX PA10

复用端口初始化有几个步骤:
1) GPIO 端口时钟使能。要使用到端口复用,当然要使能端口的时钟了。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

2) 复用的外设时钟使能。比如你要将端口 PA9,PA10 复用为串口,所以要使能串口时钟。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

3) 端口模式配置。

关于串口的内容后面的章节会详细讲解。

1.2.2 I/O重映射

为了使不同器件封装的外设 IO 功能数量达到最优,可以把一些复用功能重新映射到其他一些引脚上。 STM32 中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。 我们知道每个内置外设都有若干个输入输出引脚,一般这些引脚的输出端口都是固定不变的,为了让设计工程师可以更好地安排引脚的走向和功能,在 STM32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。

复用功能 USART1_REMAP = 0 USART1_REMAP = 1
USART1_TX PA9 PB6
USART1_RX PA10 PB7

从表中可以看出,默认情况下,串口 1 复用的时候的引脚位 PA9、PA10,同时我们可以将 TX 和 RX 重新映射到管脚 PB6 和 PB7 上面去。所以重映射我们同样要使能复用功能的时候讲解的 2 个时钟外,还要使能 AFIO 功能时钟,然后要调用重映射函数。

详细步骤为:

1) 使能 GPIOB 时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

2) 使能串口 1 时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

3) 使能 AFIO 时钟:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);

4) 开启重映射:

GPIO_PinRemapConfig(GPIO_Remap_USART1, ENABLE);

这样就将串口的 TX 和 RX 重映射到管脚 PB6 和 PB7 上面了。

1.3 GPIO流水灯硬件电路分析

发光二极管是属于二极管的一种,具有二级管单向导电特性,即只有在正向电压(二极管的正极接正,负极接负)下才能导通发光。PB0引脚接发光二极管(LED1)的正极,所以PB0引脚输出高电平LED1亮,PB0引脚输出低电平LED1熄灭,,LED2,LED3同理。

LwrOMt.png

值得注意的,不同的开发板,LED连接的GPIO一般是不同的,请注意修改。

1.4 GPIO流水灯寄存器分析

要想真正掌握一款单片机,分析寄存器是必不可少,但是对于STM32来再说,ST已经将寄存器操作封装成库函数,开发者只需要调用库函数即可,对于初学者来说,只需学会使用使用函数即可,笔者在《入门篇》也说了为何要来使用库函数以及使用库开发的好处,因此对于没有基础的读者朋友就不必细究每个寄存器,当学到一定程度,再来一探究竟吧,笔者再这里只是给出GPIO的寄存配置相关配置表,在后面的章节也是如此。好了,继续进入正题吧。

每个GPIO端口都有两个32位配置寄存器(GPIOx_CRL ,GPIOx_CRH) ,两个32位数据寄存器 (GPIOx_IDR和GPIOx_ODR),一个32位置位/ 复位寄存器(GPIOx_BSRR),一个16位复位寄存器(GPIOx_BRR),一个32位锁定寄存器(GPIOx_LCKR)。每个I/O端口位可以自由编程,然而I/O端口寄存器必须按32位字被访问(不允许半字或字节访问) 。

点亮LED,基本步骤是:配置寄存器;控制寄存器。库开发只是将传统的配置方式编程函数,是的单片机开发变得简单方便快捷。

我们常用的 IO 端口寄存器只有 4 个: CRL、 CRH、 IDR、 ODR。其中CRL 和 CRH 控制着每个 IO 口的模式及输出速率。

Lws9iQ.md.png

LwskMq.png

低配置寄存器 CRL 的描述,如下图和表下所示。

CRL和CRH类似,在此就不在赘述了,读者朋友可以参看《STM32F10XXX参考手册》数据输入输出寄存器是将对应的IO口置位,从而进行数据的输入与输出。

LwsedU.md.png

Lwsui4.png

1.5 GPIO 流水灯实现流程

笔者在上文已经分析了GPIO的原理及操作步骤,现在我们就来写代码吧。本书是用库来对STM32来开发的,这是本书的第一个实例,笔者为了读者比较直接配置寄存和库开发的区别,笔者在此用了两种方式进行开发,希望读者能自己体会两种方式的优劣。这里笔者会使用两种库:标准库和HAL库。
不管使用何种实现方式,其流程都是一样的。

GPIO是开发STM32最基本的配置,所以掌握GPIO的配置显得尤为重要。要实现流水灯,一般步骤可以总结为如下:

1) GPIO 时钟使能;
2) GPIO 端口模式设置;
3) 初始化IO口;
4) 编写处理函数;

1.6 GPIO 流水灯实现-直接操作寄存器方式

先看主函数。

/**
  * @brief     主函数
  * @param     None
  * @retval    int
  */
int main(void)
{    
    /* LED 初始化 */
    LED_GPIO_Config();     

    while(1)
    {
        // ODR GPIOB
        // 15 14 13 12     11 10 9 8    7 6 5 4    3 2 1 0
        // 0  0  0  0      0  0  0 0    0 0 0 0    0 0 0 0 (复位值)
        // 1  1  1  1      1  1  1 1    1 1 1 1    1 1 1 0
        GPIOB->ODR = 0XFFFE;         //低电平,GPIOB0(LED1)灯灭
        Delay(0x2FFFFF);
        GPIOB->ODR = 0XFFFF;        //高电平,GPIOB0(LED1)灯亮

        // ODR GPIOG
        // 15 14 13 12     11 10 9 8    7 6 5 4    3 2 1 0
        // 0  0  0  0      0  0  0 0    0 0 0 0    0 0 0 0 (复位值)
        // 1  1  1  1      1  1  1 1    1 0 1 1    1 1 1 1    
        GPIOG->ODR = 0XFFBF;         //低电平,GPIOG6(LED2)灯灭
        Delay(0x2FFFFF);

        GPIOG->ODR = 0XFFFF;        //高电平,GPIOG6(LED2)灯亮

        // ODR GPIOG
        // 15 14 13 12     11 10 9 8    7 6 5 4    3 2 1 0
        // 0  0  0  0      0  0  0 0    0 0 0 0    0 0 0 0 (复位值)
        // 1  1  1  1      1  1  1 1    0 1 1 1    1 1 1 1         
        GPIOG->ODR = 0XFF7F;         //低电平,GPIOG7(LED3)灯灭
        Delay(0x2FFFFF);
        GPIOG->ODR = 0XFFFF;        //高电平,GPIOG7(LED3)灯亮
    }

}

首先是GPIO初始化,然后就是无限循环,不断设置GPIO的高低电平。

GPIO初始化代码如下:

/**
  * @brief  初始化LED的GPIO
  * @param  None
  * @retval None
  */
void LED_GPIO_Config(void)
{
    /*开启 GPIO 时钟,使用外设时都要先开启它的时钟*/
    RCC->APB2ENR |= 1 << 3;    //使能PORTB时钟
    RCC->APB2ENR |= 1 <<8 ;    //使能PORTG时钟

    /* 配置GPIO为通用推挽输出模式,速率为50M */
    GPIOB->CRL &= 0XFFFFFFF0; 
    GPIOB->CRL |= 0X00000003;    //PB.0 推挽输出
    GPIOB->ODR |= 1 << 0;        //PB.0 输出高

    GPIOG->CRL &= 0X00FFFFFF;
    GPIOG->CRL |= 0X33000000;    //PG.6, PG.7推挽输出
    GPIOG->ODR |= 3 << 6;        //PG.6, PG.7输出高 
}

控制LED的GPIO主要做了以下事情:

1.开启GPIO时钟
2.设置GPIO的输出模式和速率,具体寄存器配置请参看CRL寄存器。
3.最后设置GPIO为高电平(非必须)。

最后在主函数不断设置GPIO的高低电平即可实现流水灯。

1.7 GPIO 流水灯实现-标准库

1.7.1 GPIO库函数

GPIO库函数相关的库函数如下:
 GPIO_DeInit 将外设 GPIOx 寄存器重设为缺省值;
 GPIO_AFIODeInit 将复用功能(重映射事件控制和 EXTI 设置)重设为缺省值;
 GPIO_Init 根据 GPIO_InitStruct 中指定的参数初始化外设 GPIOx 寄存器;
 GPIO_StructInit 把 GPIO_InitStruct 中的每一个参数按缺省值填入;
 GPIO_ReadInputDataBit 读取指定端口管脚的输入;
 GPIO_ReadInputData 读取指定的 GPIO 端口输入;
 GPIO_ReadOutputDataBit 读取指定端口管脚的输出;
 GPIO_ReadOutputData 读取指定的 GPIO 端口输出;
 GPIO_SetBits 设置指定的数据端口位;
 GPIO_ResetBits 清除指定的数据端口位;
 GPIO_WriteBit 设置或者清除指定的数据端口位;
 GPIO_Write 向指定 GPIO 数据端口写入数据;
 GPIO_PinLockConfig 锁定 GPIO 管脚设置寄存器;
 GPIO_EventOutputConfig 选择 GPIO 管脚用作事件输出;
 GPIO_EventOutputCmd 使能或者失能事件输出;
 GPIO_PinRemapConfig 改变指定管脚的映射;
 GPIO_EXTILineConfig 选择 GPIO 管脚用作外部中断线路;

1.7.2使用固件库方式

核心代码如下:

/**
  * @brief     主函数
  * @param     None
  * @retval    int
  */
int main(void)
{
    /* LED 初始化 */
    LED_GPIO_Config();

    while(1)
    {
        LED1( ON );        // 亮
        Delay(0x2fffff);
        LED1( OFF );        // 灭

        LED2( ON );        // 亮
        Delay(0x2fffff);
        LED2( OFF );        // 灭

        LED3( ON );        // 亮
        Delay(0x2ffFff);
        LED3( OFF );        // 灭
    }

}

使用库函数的流程和寄存器版的一样,唯一不同的是这里使用了库函数,而库函数也是对寄存器的封装与抽象。

1.8 GPIO 流水灯实现-HAL库

1.8.1 STM32Cube新建工程

关于如何使用STM32Cube新建工程在前文已经讲解过了,这里直说配置GPIO部分内容。本文要实现流水灯,其实输出为初始化设置为高电平还是低电平都可以,因为流水灯需要不断反转。

1.首先进行系统配置,debug模式选择可以选择其他模式,默认选择No debug,Timebase Source选择SysTick,关于Timebase Source在后文会详细讲解。

LwstoD.md.png

2.时钟配置,外部高速时钟。

LwscTS.md.png

配置系统时钟配置72MHz,APB1总线配置36MHz。默认高速时钟是使用内部(HSI),而且CPU时钟配置的比较低。以我选择的STM32F103,外部8M晶振为例(如下图)。

Lws2Fg.md.png

3.GPIO初始化配置
我们将PB0、PG6、PG7配置输出模式(高电平、低电平均可)、输出速率、上/下拉等,默认即可。

LwsTmV.md.png

4.工程生成设置

xcgGGQ.md.png

其他默认即可。最后点击“生成代码”即可生成代码。

xcgYxs.md.png

1.8.2 GPIO流水灯实现

我们要实现流水灯,需要在主函数添加一下代码。

HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);

HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);

HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
HAL_Delay(500);
HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);

xcgNMn.md.png

1.8.3流水灯代码分析

在分析代码前,先看看流水灯编程流程:
1>使能GPIO端口时钟;
2>初始化GPIO引脚,即为GPIO初始化结构体赋值,并调用相应初始化函数完成初始化配置;
3>根据实际需求控制流水灯。

STM32采用固件库开发,代码比较多,因此,我们在看一个实际工程时,只需从主函数开始,好了,接下来,笔者就带领大家一步一步看看流水灯是怎么实现的。笔者先贴出主函数代码。

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);

    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);

    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
  }
  /* USER CODE END 3 */
}

注释部分不用管,我们只看代码。

1.复位所有硬件
HAL_Init()函数为复位所有硬件,这个就不细说了。

HAL_StatusTypeDef HAL_Init(void)
{
  /* Configure Flash prefetch */
#if (PREFETCH_ENABLE != 0)
#if defined(STM32F101x6) || defined(STM32F101xB) || defined(STM32F101xE) || defined(STM32F101xG) || \
    defined(STM32F102x6) || defined(STM32F102xB) || \
    defined(STM32F103x6) || defined(STM32F103xB) || defined(STM32F103xE) || defined(STM32F103xG) || \
    defined(STM32F105xC) || defined(STM32F107xC)

  /* Prefetch buffer is not available on value line devices */
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
#endif
#endif /* PREFETCH_ENABLE */

  /* Set Interrupt Group Priority */
  HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);

  /* Use systick as time base source and configure 1ms tick (default clock after Reset is HSI) */
  HAL_InitTick(TICK_INT_PRIORITY);

  /* Init the low level hardware */
  HAL_MspInit();

  /* Return function status */
  return HAL_OK;
}

2.时钟初始化函数
SystemClock_Config()函数用于时钟初始化,也就是通过STM32Cube实现。

LwscTS.md.png

时钟初始化函数如下:

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

RCC_OscInitTypeDef结构体类型定义时钟来源和系统时钟生成配置,就是图9中的配置,本例程使用外部8MHz晶振,通过PLL锁相环9倍频后得到72M系统时钟。

RCC_ClkInitTypeDef结构体类型定义总线时钟配置,一般选择使能系统时钟、AHB、APB1和APB2总线时钟,其中只有APB1总线时钟36MHz,其他都为72MHz。
HAL_RCC_ClockConfig函数就是HAL定义的一个系统滴答定时器初始化配置函数,通过这个函数得到延时效果。

3.GPIO初始化函数

MX_GPIO_Init()函数用于LED的GPIO初始化,代码如下:

static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOG_CLK_ENABLE();

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOG, GPIO_PIN_6|GPIO_PIN_7, GPIO_PIN_RESET);

  /*Configure GPIO pin : PB0 */
  GPIO_InitStruct.Pin = GPIO_PIN_0;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

  /*Configure GPIO pins : PG6 PG7 */
  GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

}

以上就是LED的初始化函数,初始化GPIO有个重要的结构体GPIO_InitTypeDef,先看看该结构体。

typedef struct
{
  uint32_t Pin;       /*!< Specifies the GPIO pins to be configured.
                           This parameter can be any value of @ref GPIO_pins_define */
  uint32_t Mode;      /*!< Specifies the operating mode for the selected pins.
                           This parameter can be a value of @ref GPIO_mode_define */
  uint32_t Pull;      /*!< Specifies the Pull-up or Pull-Down activation for the selected pins.
                           This parameter can be a value of @ref GPIO_pull_define */
  uint32_t Speed;     /*!< Specifies the speed for the selected pins.
                           This parameter can be a value of @ref GPIO_speed_define */
} GPIO_InitTypeDef;

 Pin:引脚编号选择,一个GPIO有16个引脚可选,参数可选有:GPIO_PIN_0、….、GPIO_PIN_15和GPIO_PIN_ALL。很多时候我们可以使用或运算进行选择:GPIO_PIN_0| GPIO_PIN_4。
 Mode:引脚模式选择,在前文笔者已经介绍了引脚有8中工作模式,根据外设选择相应模式。

Lw6eC4.md.png

 Pull:上下拉,用于输入模式,可选:GPIO_NOPULL不上下拉;GPIO_PULLUP:使能下拉;GPIO_PULLDOWN使能下拉;
 Speed:引脚速度,可选:GPIO_SPEED_FREQ_LOW:低速(2MHz);中速(10MHz);高速(50MHz)。

在初始化代码中,还有一个重要的函数HAL_GPIO_WritePin()。HAL_GPIO_WritePin()函数为3个LED灯时钟初始化状态,这里设置为低电平,所以初始化状态3个LED都是暗的。

4.流水灯实现
在前文已经贴出了流水灯的相应代码。

while (1)
{
    /* USER CODE END WHILE */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);

    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_6);

    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);
    HAL_Delay(500);
    HAL_GPIO_TogglePin(GPIOG, GPIO_PIN_7);

    /* USER CODE BEGIN 3 */
}

HAL_GPIO_TogglePin函数用于GPIO电平反转,HAL_Delay用于延时,单位是毫秒,以此不断将LED1、LED2、LED3关灭,这就是实现流水灯的效果。

1.9实验现象

将编译好的程序下载到板子中,可以看到三个LED灯不同地闪烁。

本章虽然用不同的方式实现了流水灯,也就是不断控制GPIO的高低电平,我想大家会有很多疑问,寄存器、表混库、HAL的区别是什么,流水灯是怎么一步步调用等等,后面的章节会一一解答,尽请期待!

小贴士:上拉电阻

1.当 TTL 电路驱动 CMOS 电路时,如果 TTL 电路输出的高电平低于 CMOS电路的最低高电平(一般为 3.5V),这时就需要在 TTL 的输出端接上拉电阻,以提高输出高电平的值。

2.OC(集电极开路)门电路必须加上拉电阻,才能使用。

3.为加大输出引脚的驱动能力,有的单片机管脚上也常使用上拉电阻。

4.在 CMOS 芯片上,为了防止静电造成损坏,不用的管脚不能悬空,一般接上拉电阻产生降低输入阻抗,提供泄荷通路。

5.芯片的管脚加上拉电阻来提高输出电平,从而提高芯片输入信号的噪声容限增强抗干扰能力。

6.提高总线的抗电磁干扰能力。管脚悬空就比较容易接受外界的电磁干扰。

7.长线传输中电阻不匹配容易引起反射波干扰,加上下拉电阻是电阻匹配,有效的抑制反射波干扰。

 上拉电阻阻值的选择原则包括:

 从节约功耗及芯片的灌电流能力考虑应当足够大;电阻大,电流小。
 从确保足够的驱动电流考虑应当足够小;电阻小,电流大。
 对于高速电路,过大的上拉电阻可能边沿变平缓。综合考虑

以上三点,通常在 1k 到 10k 之间选取。对下拉电阻也有类似道理对上拉电阻和下拉电阻的选择应结合开关管特性和下级电路的输入特性进行设定,主要需要考虑以下几个因素:
 驱动能力与功耗的平衡。以上拉电阻为例,一般地说,上拉电阻越小,驱动能力越强,但功耗越大,设计是应注意两者之间的均衡。

 下级电路的驱动需求。同样以上拉电阻为例, 当输出高电平时,开关管断开,上拉电阻应适当选择以能够向下级电路提供足够的电流。
 高低电平的设定。不同电路的高低电平的门槛电平会有不同,电阻应适当设定以确保能输出正确的电平。以上拉电阻为例,当输出低电平时,开关管导通,上拉电阻和开关管导通电阻分压值应确保在零电平门槛之下。
 频率特性。以上拉电阻为例,上拉电阻和开关管漏源级之间的电容和下级电路之间的输入电容会形成 RC 延迟,电阻越大,延迟越大。上拉电阻的设定应考虑电路在这方面的需求。

 下拉电阻的设定的原则和上拉电阻是一样的

Related posts

Leave a Comment